#include <cmath>
#include "OrderBook.hpp"

namespace FeedHandler
{
	OrderBook::OrderBook(OnNewTradeCallback::Ptr & onNewTrade)
	{
		m_callback = onNewTrade;
	}

    void OrderBook::addOrder(const Order & order)
    {
        // looking for order with such id
        OrdersSet::iterator iOrder = m_orders.find(order);
        
        if (iOrder == m_orders.end())
        {
            // inserting order to set
			iOrder = m_orders.insert(order).first;

			// inserting order to buy or sell order book
			if (iOrder->side == Order::Buy)
			{
				// inserting new element
				m_buyOrderBook[iOrder->price].push_back(order);
			}
			else if (iOrder->side == Order::Sell)
			{
				// inserting new element
				m_sellOrderBook[iOrder->price].push_back(order);
			}
			else
			{
				// TODO: error
			}
        }
        else
        {
            // Error: trying to add order with same id
			throw std::exception("trying to add order with same id");
        }
    }

    void OrderBook::deleteOrder(const Order & order)
    {
        // looking for order with such id
        OrdersSet::iterator iOrder = m_orders.find(order);
        
        if (iOrder != m_orders.end())
        {
			if (iOrder->side == Order::Buy)
			{
				eraseOrderFromQueue(m_buyOrderBook, order);

				// erasing order from set
				m_orders.erase(iOrder);
			}
			else if (iOrder->side == Order::Sell)
			{
				eraseOrderFromQueue(m_sellOrderBook, order);

				// erasing order from set
				m_orders.erase(iOrder);
			}
			else
			{
				// TODO: error
			}
        }
        else
        {
            // Error: trying to remove order which is not in set
			throw std::exception("trying to remove order which is not in set");
        }
    }

    void OrderBook::modifyOrder(const Order & order)
    {
        // looking for order with such id
        OrdersSet::iterator iOrder = m_orders.find(order);
        
        if (iOrder != m_orders.end())
        {
			if (iOrder->side == Order::Buy)
			{
				modifyOrderInQueue(m_buyOrderBook, order);
			}
			else if (iOrder->side == Order::Sell)
			{
				modifyOrderInQueue(m_sellOrderBook, order);
			}
			else
			{
				// TODO: error
			}
        }
        else
        {
            // Error: trying to remove order which is not in set
			throw std::exception("trying to remove order which is not in set");
        }
    }

	void OrderBook::trade(const Trade & trade)
	{
		(*m_callback)(trade);
	}

	void OrderBook::eraseOrderFromQueue(OrderBookMap & ob, const Order & order)
	{
		// TODO: this code is not efficient - complexity for search is O(N)
		OrderBookMap::iterator iObEntry = ob.find(order.price);

		if (iObEntry != ob.end())
		{
			OrdersQueue & q = iObEntry->second;

			for (OrdersQueue::iterator i = q.begin(), iEnd = q.end(); i != iEnd; ++i)
			{
				if (i->id == order.id)
				{
					// order is founded, erasing it
					q.erase(i);
					break;
				}
			}

			if (q.empty())
			{
				ob.erase(iObEntry);
			}
		}
		else
		{
			// TODO: something went wrong...
		}
	}

	void OrderBook::modifyOrderInQueue(OrderBookMap & ob, const Order & order)
	{
		// TODO: this code is not efficient - complexity for search is O(N)
		OrderBookMap::iterator iObEntry = ob.find(order.price);

		if (iObEntry != ob.end())
		{
			OrdersQueue & q = iObEntry->second;

			for (OrdersQueue::iterator i = q.begin(), iEnd = q.end(); i != iEnd; ++i)
			{
				if (i->id == order.id)
				{
					// order is founded, erasing it
					*i = order;
					break;
				}
			}
		}
		else
		{
			// TODO: something went wrong...
		}
	}

	void OrderBook::printSideOrderBook(const OrderBookMap & ob, std::stringstream & ss)
	{
		for (OrderBookMap::const_reverse_iterator i = ob.rbegin(), iEnd = ob.rend(); i != iEnd; ++i)
		{
			const OrdersQueue & q = i->second;

			ss << i->first << " ";

			for (OrdersQueue::const_iterator j = q.begin(), jEnd = q.end(); j != jEnd; ++j)
			{
				ss << static_cast<char>(j->side) << " " << j->quantity << " ";
			}

			ss << std::endl;
		}
	}

	std::string OrderBook::toString()
	{
		std::stringstream ss;

		// printing sell order book
		printSideOrderBook(m_sellOrderBook, ss);

		// printing buy order book
		printSideOrderBook(m_buyOrderBook, ss);

		return ss.str();
	}

	double OrderBook::calculateMidQuote()
	{
		if (m_buyOrderBook.empty() || m_sellOrderBook.empty())
		{
			return std::numeric_limits<double>::quiet_NaN();
		}

		// (biggest buy + lowest sell) / 2
		return 0.5 * (m_buyOrderBook.rbegin()->first + m_sellOrderBook.begin()->first);
	}
}
